/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	Material.cpp
*
*	Comments	-	See Material.h
*
**************************************************************************************/
#include "../Include/Material.h"
#include "../Include/Effect.h"
#include "../Include/XMLParser.h"
#include "../Include/GlobalDirectory.h"
#include "../Include/MaterialElementInfo.h"
#include "../Include/MaterialElementEntry.h"
#include "../Include/ParameterSystem.h"
#include "../Include/Engine.h"
#include "../Include/Tokenizer.h"
#include "../Include/GraphicsResourceManager.h"

namespace Pulse
{
	Material::Material( void )
		: m_pEffect( PSX_NULL )
	{

	}

	Material::~Material( void )
	{

	}

	BOOL Material::CreateResource( BaseResDesc *pDesc )
	{
		if ( pDesc->GetType() != GetType() )
			return FALSE;

		m_desc = *((MaterialDesc*)pDesc);

		if ( PerformInitialize( m_desc.IsAsyncLoad() ) == EErrorCode::OKAY )
			SetFlagCreated();

		return IsCreated();
	}

	BOOL Material::DestroyResource( void )
	{
		UnloadResource();
		SetFlagDestroyed();	
		return TRUE;	
	}

	BOOL Material::LoadResource( void )
	{
		if ( Initialize() == EErrorCode::OKAY )
		{
			SetFlagLoaded();	
			return TRUE;	
		}

		return FALSE;
	}

	BOOL Material::UnloadResource( void )
	{
		Cleanup();
		SetFlagUnloaded();
		return TRUE;	
	}

	Effect * Material::GetEffect( void )
	{
		m_pEffect->AddRef();

		return m_pEffect;
	}

	BOOL Material::IsMaterialElementAvailable( EParameter::Type type, const CHAR *pName )
	{
		// Check global then submaterial parameters
		if ( IsMaterialElementInContainer( &m_params, type, pName ) )
		{
			return TRUE;
		}

		for ( SIZE_T i = 0; i < m_subMaterials.GetSize(); ++i )
		{
			if ( IsMaterialElementInContainer( &m_subMaterials[i]->m_params, type, pName ) )
			{
				return TRUE;
			}
		}

		return FALSE;
	}

	IParameterBinder * Material::GetMaterialElemendBinder( EParameter::Type type, const CHAR *pName )
	{
		if ( pName == PSX_NULL )
		{
			return PSX_NULL;
		}

		EParameter::Type typeToCheck;
		SIZE_T numBinders = m_params.GetNumParameterBinders();

		switch( type )
		{
		case EParameter::VECTOR3:
			typeToCheck = EParameter::VECTOR3;
			break;
		case EParameter::VECTOR4:
			typeToCheck = EParameter::VECTOR4;
			break;
		case EParameter::MATRIX:
			typeToCheck = EParameter::MATRIX;
			break;
		case EParameter::MATRIX_ARRAY:
			typeToCheck = EParameter::MATRIX_ARRAY;
			break;
		case EParameter::TEXTURE2D:
		case EParameter::TEXTURE3D:
			//typeToCheck = EParameter::TEXTURE3D;
			typeToCheck = EParameter::SHADER_RESOURCE;
			break;
		default:
			PSX_PushWarning( "Material element type to Parameter type mapping not yet supported." );
			typeToCheck = EParameter::UNKNOWN;
		}

		if ( typeToCheck == EParameter::UNKNOWN )
		{
			return PSX_NULL;
		}

		for ( SIZE_T i = 0; i < numBinders; ++i )
		{
			IParameterBinder *pBinder = m_params.GetParameterBinder( (UINT)i );
		
			if ( pBinder->GetType() == typeToCheck )
			{
				if ( PSX_StrCmp( pBinder->GetParameter()->GetName(), pName ) == 0 )
				{
					return pBinder;
				}
			}
		}

		return PSX_NULL;
	}

	ParameterContainer * Material::GetSubMaterialParameters( UPOS_T index ) 
	{
		if ( index < m_subMaterials.GetSize() )
		{
			return &m_subMaterials[index]->m_params;
		}
		else
		{
			return PSX_NULL;
		}
	}

	EErrorCode::Type Material::AddParameter( const CHAR *pResSysName, Texture2D *pRes )
	{


		return EErrorCode::OKAY;
	}

	EErrorCode::Type Material::Initialize( void )
	{
		// Load filename?
		if ( m_desc.m_filename.IsEmpty() )
		{
			PSX_PushError( "Filename empty." );
			return EErrorCode::INVALID_PARAMETER;
		}

		XMLDocumentPtr pDoc = XMLParser::Load( (PSX_PATH_MATERIALS + m_desc.m_filename).GetBuffer() );

		if ( pDoc.IsNull() )
		{
			PSX_PushError( "Failed to load material file." );
			return EErrorCode::_ERROR;
		}

		XMLElementPtr pElem = pDoc->FindChild( PSX_String("Material") );

		if ( pElem.IsValid() )
		{
			if ( ProcessMaterial( pElem ) != EErrorCode::OKAY )
				return EErrorCode::_ERROR;
		}
		else
		{
			PSX_PushError( "No Material info." );
			return EErrorCode::INVALID_FORMAT;
		}


		return EErrorCode::OKAY;
	}

	void Material::Cleanup( void )
	{
		PSX_SafeRelease( m_pEffect );

		for ( SIZE_T i = 0; i < m_subMaterials.GetSize(); ++i )
			delete m_subMaterials[i];

		m_subMaterials.Clear();
	}

	EErrorCode::Type Material::ProcessMaterial( XMLElementPtr pElem )
	{
		// Check global parameters first 
		EErrorCode::Type retCode;
		XMLElementPtr pChild = pElem->FindChild( PSX_String("Effect") );
		
		// An assigned effect is required
		if ( pChild.IsNull() )
		{
			PSX_PushError( "No assigned effect to this material." );
			return EErrorCode::INVALID_FORMAT;
		}

		retCode = ProcessEffect( pChild );
		
		if ( retCode != EErrorCode::OKAY )
			return EErrorCode::_ERROR;

		pChild = pElem->FindChild( PSX_String("Parameters") );
		
		if ( pChild.IsValid() )
		{
			retCode = ProcessParameters( pChild, &m_params );

			if ( retCode != EErrorCode::OKAY )
				return EErrorCode::_ERROR;
		}

		// Then process submaterials
		pChild = pElem->FindChild( PSX_String("SubMaterials") );
		
		if ( pChild.IsValid() )
		{
			ProcessSubMaterials( pChild );

			if ( retCode != EErrorCode::OKAY )
				return EErrorCode::_ERROR;
		}

		return EErrorCode::OKAY;
	}

	EErrorCode::Type Material::ProcessEffect( XMLElementPtr pElem )
	{
		XMLElementPtr pChild = pElem->FindChild( PSX_String("File") );
		
		if ( pChild.IsNull() )
		{
			PSX_PushError( "No file info in geometry info." );
			return EErrorCode::INVALID_FORMAT;
		}

		GraphicsResourceManager *pGM = GraphicsResourceManager::GetInstance();
		String filename = pChild->GetValue();
		EffectDesc desc;
		
		m_pEffect = pGM->FindEffect( filename.GetBuffer() );

		if ( m_pEffect == PSX_NULL )
		{
			PSX_PushError( "Can't find effect file requested. Failed to create geometry." );
			return EErrorCode::_ERROR;
		}
		return EErrorCode::OKAY;
	}

	EErrorCode::Type Material::ProcessParameters( XMLElementPtr pElem, ParameterContainer *pParams )
	{
		return pParams->ParseParameters( pElem );
	}

	EErrorCode::Type Material::ProcessSubMaterials( XMLElementPtr pElem )
	{
		EErrorCode::Type retCode;
		XMLElementPtr pChild = pElem->GetFirstChild();
		
		// For each SubMaterial
		while ( pChild.IsValid() )
		{
			if ( pChild->GetName() == PSX_String( "SubMaterial" ) )
			{
				XMLElementPtr pParameters = pChild->FindChild( PSX_String("Parameters") );

				if ( pParameters.IsValid() )
				{
					SubMaterial *pNewMaterial = new SubMaterial;
					retCode = ProcessParameters( pParameters, &pNewMaterial->m_params );
					
					if ( retCode != EErrorCode::OKAY )
					{
						return EErrorCode::_ERROR;
					}

					m_subMaterials.PushBack( pNewMaterial );
				}
			}

			pChild = pChild->GetNextSibling();
		}

		return EErrorCode::OKAY;
	}

	BOOL Material::IsMaterialElementInContainer(  ParameterContainer *pContainer, EParameter::Type type, const CHAR *pName )
	{
		EParameter::Type typeToCheck;
		SIZE_T numBinders = pContainer->GetNumParameterBinders();

		switch( type )
		{
		case EParameter::VECTOR3:
			typeToCheck = EParameter::VECTOR3;
			break;
		case EParameter::VECTOR4:
			typeToCheck = EParameter::VECTOR4;
			break;
		case EParameter::MATRIX:
			typeToCheck = EParameter::MATRIX;
			break;
		case EParameter::MATRIX_ARRAY:
			typeToCheck = EParameter::MATRIX_ARRAY;
			break;
		case EParameter::TEXTURE2D:
		case EParameter::TEXTURE3D:
			//typeToCheck = EParameter::TEXTURE3D;
			typeToCheck = EParameter::SHADER_RESOURCE;
			break;
		default:
			PSX_PushWarning( "Material element type to Parameter type mapping not yet supported." );
			typeToCheck = EParameter::UNKNOWN;
		}

		if ( typeToCheck == EParameter::UNKNOWN )
			return FALSE;

		for ( SIZE_T i = 0; i < numBinders; ++i )
		{
			IParameterBinder *pBinder = pContainer->GetParameterBinder( (UINT)i );
		
			if ( pBinder->GetType() == typeToCheck )
			{
				if ( PSX_StrCmp( pBinder->GetParameter()->GetName(), pName ) == 0 )
				{
					return TRUE;
				}
			}
		}

		return FALSE;
	}

}